from datetime import datetime
from django.db import models
from django import forms
from django.contrib.auth.models import User
import const, conf

# Create your models here.
class TestRun(models.Model):  
    TESTRUN_PRI_CHOICES = [(i, i) for i in range(1, 9)]

    name = models.CharField(max_length=64, unique=True)
    priority = models.IntegerField(
      max_length=1, 
      choices=TESTRUN_PRI_CHOICES, 
      default=5)

    def __unicode__(self):
        return self.name

class OSRelease(models.Model):
    name = models.CharField(max_length=32, unique=True)
    description = models.CharField(max_length=128)

    def __unicode__(self):
        return self.name


class MGroup(models.Model):
    MACHINE_TYPE_CHOICES = (
        (const.MACHINE_TYPE_AUTOTEST, 'autotest'),
        (const.MACHINE_TYPE_MANTEST, 'mantest'),
        (const.MACHINE_TYPE_OTHER, 'other'),
    ) 

    INTG_TYPE_CHOICES = ( 
        (const.INTG_TYPE_PRE, 'pre'),
        (const.INTG_TYPE_POST, 'post'),
    ) 

    DTBLD_TYPE_CHOICES = (  
      (const.DTBLD_TYPE_STABLE, 'stable'),
      (const.DTBLD_TYPE_DEVEL, 'devel'),
      (const.DTBLD_TYPE_DEFAULT, 'default'),
    )

    name = models.CharField(max_length=64)
    machine_type = models.IntegerField(
      max_length=1, 
      choices=MACHINE_TYPE_CHOICES,
      default=const.MACHINE_TYPE_AUTOTEST)
    os_release = models.ForeignKey(OSRelease)
    # dtbld_type(i.e. dt build type) = dt build branch
    dtbld_type = models.IntegerField(
      max_length=1, 
      choices=DTBLD_TYPE_CHOICES,
      default=const.DTBLD_TYPE_DEFAULT)
    intg_type = models.IntegerField(
      max_length=1, 
      choices=INTG_TYPE_CHOICES)

    testrun = models.ManyToManyField(TestRun, blank=True)

    def __unicode__(self):
        return self.name

class Location(models.Model):  
    name = models.CharField(max_length=32)

    def __unicode__(self):
        return self.name

class Site(models.Model):
    name = models.CharField(max_length=64, unique=True)
    domain = models.CharField(max_length=64)
    timezone = models.CharField(max_length=16)
    host_eg = models.CharField(max_length=64)

    def __unicode__(self):
        return self.name

class IPSRepo(models.Model):
    site = models.ForeignKey(Site)
    os_release = models.ForeignKey(OSRelease)
    os_ips_repo = models.CharField(max_length=128)
    wox_ips_repo = models.CharField(max_length=128)
    dt_ips_repo = models.CharField(max_length=128)
    dt_dev_ips_repo = models.CharField(max_length=128, blank=True)
    
    def __unicode__(self):
        return "%s_%s" % (self.site.name, self.os_release.name)
    class Meta:
        unique_together = (("site", "os_release"),)


class HWMode(models.Model):
    ARCH_CHOICES = (
        (const.ARCH_X86, 'x86'),
        (const.ARCH_SPARC, 'sparc'),
    ) 
    name = models.CharField(max_length=64)
    arch = models.IntegerField(max_length=1, choices=ARCH_CHOICES)

    def __unicode__(self):
        return self.name


class Machine(models.Model):
    LIFECYCLE_CHOICES = (
        (const.MSTATUS_INITIAL, 'initial'),
        (const.MSTATUS_INSTALL, 'install'),
        (const.MSTATUS_UPDATE, 'update'),
        (const.MSTATUS_READY, 'ready'),
        (const.MSTATUS_RUNNING, 'running'),
        (const.MSTATUS_ERROR, 'error'),
        (const.MSTATUS_UNREACHABLE, 'unreachable'),
        (const.MSTATUS_NOTCTRL, 'notcontrol'),
    ) 

    status = models.IntegerField(
      max_length=3, 
      choices=LIFECYCLE_CHOICES,
      default=const.MSTATUS_INITIAL)
    pre_status = models.IntegerField(
      max_length=3, 
      choices=LIFECYCLE_CHOICES,
      default=const.MSTATUS_INITIAL)
    last_status_update_time = models.DateTimeField(auto_now_add=True, blank=True, null=True)

    hostname = models.CharField(max_length=64, unique=True)
    owner = models.ForeignKey(User)
    mgroup = models.ForeignKey(MGroup)
    site = models.ForeignKey(Site)
    hwmode = models.ForeignKey(HWMode)
    notification_email = models.CharField(max_length=256, blank=True)
    reserve_email = models.EmailField(blank=True)

    os_bldno = models.CharField(max_length=32, blank=True)
    dt_bldno = models.CharField(max_length=32, blank=True)
    bldno = models.CharField(max_length=32, blank=True)

    def __unicode__(self):
        return self.hostname

    def set_status(self, new_stat):
        self.pre_status = self.status
        self.status = new_stat

    def rollback_status(self, pre_stat):
        self.status = self.pre_status
        self.pre_status = pre_stat

    def get_pre_status(self):
        return self.pre_status

    def get_status(self):
        return self.status

    # check if machine is unreachable and update the status if necessary,
    # return the proper status at the end. Use this function if it's not
    # sure that the machine is reachable, otherwise use get_status()
    def check_status(self, save=True):
	if self.status in (const.MSTATUS_INITIAL, const.MSTATUS_NOTCTRL):
            return self.status

        is_timeout = \
          (datetime.utcnow() - self.last_status_update_time).seconds > \
          const.MSTATUS_TIMEOUT[self.status]

        if self.status == const.MSTATUS_UNREACHABLE:
            if not is_timeout:
                # the system is back now 
                if self.pre_status in (const.MSTATUS_INSTALL,
                                       const.MSTATUS_UPDATE):
                    self.status = const.MSTATUS_ERROR
                elif self.pre_status == const.MSTATUS_RUNNING:
                    # clear test env.: kill/remove stuffs owned by previous testrun 
                    #FIXME: should we restart the job/testrun? 
                    self.status = const.MSTATUS_READY
                else:
                    self.status = self.pre_status

                self.pre_status = const.MSTATUS_INITIAL
                if save:
                    #self.save(update_fields=['status', 'pre_status'])
                    self.save()
        else:
            if is_timeout:
                # if timeout, backup current status and 
                # set status as unreachable
                self.pre_status = self.status
                self.status = const.MSTATUS_UNREACHABLE

                #FIXME: change job/testrun status and save them for restart later
                if save:
                    #self.save(update_fields=['status', 'pre_status'])
                    self.save()

        return self.status


class HWConf(models.Model):
    hwmode = models.ForeignKey(HWMode)
    # physical processor number
    psr_pnumber = models.IntegerField(blank=True, null=True)
    # virtual processor number
    psr_vnumber = models.IntegerField(blank=True, null=True)
    # clock speed 
    psr_ckspeed = models.CharField(max_length=32, blank=True)
    # memory size
    mem_size = models.CharField(max_length=32, blank=True)

    def __unicode__(self):
        return unicode(self.id)


class MachineMore(models.Model):
    IP_TYPE_CHOICES = (  
        (const.IPADDR_TYPE_STATIC, 'static'),
        (const.IPADDR_TYPE_DHCP, 'dhcp'),
    ) 

    machine = models.OneToOneField(Machine, primary_key=True)
    mac = models.CharField(max_length=32)
    ipaddr_type = models.IntegerField(
      max_length=1, 
      choices=IP_TYPE_CHOICES,
      default=const.IPADDR_TYPE_STATIC)

    location = models.ForeignKey(Location)
    serial_no = models.CharField(max_length=32, blank=True)
    sp_hostname = models.CharField(max_length=64, blank=True)
    comments = models.TextField(blank=True)

    hwconf = models.ForeignKey(HWConf, blank=True, null=True)

    # if true, the machine will be autoinstalled by cron when
    # new build is ready
    autoinst_flag = models.BooleanField(default=True)
    # how many times the system continuously upgrade
    # it's reset to 0 once it initially install
    update_inst_times = models.IntegerField(max_length=2, default=0)
    update_inst_times_max = models.IntegerField(
        max_length=2, 
        default=const.UPDATE_INST_TIMES_MAX_DEFAULT)

    def __unicode__(self):
        return self.machine.hostname

"""
Job is scheduled/created by 
  1. /bg/ai
  2. web ui: exec

NOW type of jobs are started immediately while other types
of jobs are schuduled. Schduled jobs are started when:
  1. install complete
  2. previous job complete
  3. cronjob times up(for NEXTAFTER and FIXED)
  4. test machine recovers from unreachable??
"""
class Job(models.Model):  
    JOB_PRI_CHOICES = [(i, i) for i in range(1, 9)]
    JSTATUS_CHOICES = (
        (const.JSTATUS_SCHEDULED, 'scheduled'),
        (const.JSTATUS_STARTED, 'started'),
        (const.JSTATUS_FINISHED, 'finished'),
        (const.JSTATUS_TERMINATED, 'terminated'),
        (const.JSTATUS_CANCELLED, 'cancelled'),
    ) 
    JSTART_TYPE_CHOICES = (
        (const.JSTART_TYPE_NOW, 'right now'),
        (const.JSTART_TYPE_NEXT, 'next build'),
        (const.JSTART_TYPE_NEXTAFTER, 'next build after'),
        (const.JSTART_TYPE_FIXED, 'specified date'),
    )

    owner = models.ForeignKey(User)
    status = models.IntegerField(
      max_length=2, 
      choices=JSTATUS_CHOICES, 
      default=const.JSTATUS_SCHEDULED)

    priority = models.IntegerField(
      max_length=1, 
      choices=JOB_PRI_CHOICES, 
      default=5)

    dryrun_flag = models.BooleanField(default=False)

    # flag for recording if the job is executed
    # by the designated test machine
    dtm_flag = models.BooleanField(default=False)

    start_type = models.IntegerField(max_length=1, 
                                      choices=JSTART_TYPE_CHOICES,
                                      default=const.JSTART_TYPE_NOW)
    # it's used only when timing_type is NEXTAFTER
    # this field has another usage for NEXTAFTER and FIXED
    # when it's time to run NEXTAFTER/FIXED job but the machine
    # is not ready, set it as JOB_CRON_TIMEUP_FLAG
    start_delay_hours = models.IntegerField(default=0)

    start_time = models.DateTimeField(blank=True, null=True)
    end_time = models.DateTimeField(blank=True, null=True)

    test_machine = models.ForeignKey(Machine)
    hwconf = models.ForeignKey(HWConf, blank=True, null=True)

    mgroup = models.ForeignKey(MGroup)
    bldno = models.CharField(max_length=16, blank=True, null=True)

    # testrun queue in this schema: 
    #   tid|testrun_name|tid|testrun_name|...
    queue = models.CharField(max_length=256, default="")

    def pop(self, curr_tid=None):
        rv1 = None
        rv2 = ""

        q = self.queue.split(conf.WSA_MFS)
        tidq = map(int, q[::2])
        tnmq = q[1::2]
        if not curr_tid:
            # return first testrun if no curr_tid specified
            (rv1, rv2) = (tidq[0], tnmq[0])
        elif curr_tid in tidq:
            # note: there might be ValueError exception
            # leave it to outer try-except 
            i = tidq.index(curr_tid) + 1
            # check the next testrun in the queue
            if len(tidq) > i:
                (rv1, rv2) = (tidq[i], tnmq[i])

        return (rv1, rv2)


    def __unicode__(self):
        return unicode(self.id)
    class Admin:
        pass


class TestRunExec(models.Model):  
    TSTATUS_CHOICES = (
        (const.TSTATUS_SCHEDULED, 'scheduled'),
        (const.TSTATUS_STARTED, 'started'),
        (const.TSTATUS_FINISHED, 'finished'),
        (const.TSTATUS_ERROR, 'error'),
        (const.TSTATUS_TERMINATED, 'terminated'),
        (const.TSTATUS_TIMEOUT, 'timeout'),
        (const.TSTATUS_CANCELLED, 'cancelled'),
    ) 

    job = models.ForeignKey(Job)

    testrun = models.ForeignKey(TestRun)
    status = models.IntegerField(
      max_length=2, 
      choices=TSTATUS_CHOICES, 
      default=const.TSTATUS_SCHEDULED)

    start_time = models.DateTimeField(blank=True, null=True)
    end_time = models.DateTimeField(blank=True, null=True)

    report_id = models.IntegerField(max_length=32, blank=True, null=True)
    log_path = models.CharField(max_length=64, blank=True)

    def __unicode__(self):
        return unicode(self.id)
    class Admin:
        pass


class DesignatedTestMachine(models.Model):
    mgroup = models.ForeignKey(MGroup)
    arch = models.IntegerField(max_length=1, choices=HWMode.ARCH_CHOICES)
    default = models.ForeignKey(Machine)

    def __unicode__(self):
        return "%s_%s" % (self.mgroup.name, self.get_arch_display())
    class Meta:
        unique_together = (('mgroup', 'arch'),)

# coming soon
#class Installation:
#    pass

